﻿using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
using System.Runtime.Serialization.Formatters.Binary;

namespace NazTek.Extension.Clr4
{
    /// <summary> 
    /// Available options to format a string 
    /// </summary> 
    /// <remarks></remarks> 
    public enum FormatOption
    {
        /// <summary> 
        /// Base: 32 | Formatter: binary 
        /// </summary> 
        /// <remarks></remarks> 
        Base32Binary,
        /// <summary> 
        /// Base: 64 | Formatter: binary 
        /// </summary> 
        /// <remarks></remarks> 
        Base64Binary,
        /// <summary> 
        /// Base: 32 | Formatter: soap 
        /// </summary> 
        /// <remarks></remarks> 
        Base32Soap,
        /// <summary> 
        /// Base: 64 | Formatter: soap 
        /// </summary> 
        /// <remarks></remarks> 
        Base64Soap,
        /// <summary>
        /// Plain old xml
        /// </summary>
        Pox
    }
    /// <summary> 
    /// Object extension methods 
    /// </summary> 
    /// <remarks></remarks> 
    public static class ObjectExtension
    {
        /// <summary> 
        /// Serializes an object to string. The object must be serializable via Serializable attribute or ISerializable interface 
        /// </summary> 
        /// <param name="serializableInstance">The instance to serialize</param> 
        /// <param name="formatOption">Formatting to use, e.g., base 32/64, soap/binary, and pox</param> 
        /// <returns>A string representation of the the object</returns> 
        /// <remarks>If the serialized string contains space, the serialization operation is performed again, upto 3 times and if the problem 
        /// persists, an exception is thrown</remarks> 
        /// <exception cref="SerializationException">Thrown when the type being serialized to string is not marked serializable</exception>
        /// <example>
        /// <code>
        /// // context
        /// [Serializable]
        /// public class Person
        /// {
        ///     public string Name{get;set;}
        ///     public Person(string pName) { Name = pName; }
        /// }
        /// 
        /// // usage
        /// // \0\0\0\0ÿÿÿÿ\0\0\0\0\0\0\0\f\0\0\0UEnterprise.Core.Extension.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
        /// // \0\0\0>NazTek.Extension.Clr4.Test.GenericListExtensionTest+Person\0\0\0&lt;Name&gt;k__BackingField\0\0\0\0\0\0Bill\v
        /// string s = new Person("Bill").ToString(FormatOption.Base32Binary);
        /// 
        /// // &lt;SOAP-ENV:Envelope 
        /// //   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
        /// //   xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" 
        /// //   xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" 
        /// //   xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" 
        /// //   xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0\" 
        /// //   SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"&gt;
        /// //   &lt;SOAP-ENV:Body&gt;
        /// //     &lt;a1:GenericListExtensionTest_x002B_Person 
        /// //       id=\"ref-1\" 
        /// //       xmlns:a1=\"http://schemas.microsoft.com/clr/nsassem/NazTek.Extension.Clr4.Test/NazTek.Extension.Clr4.Test%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull\"&gt;
        /// //       &lt;_x003C_Name_x003E_k__BackingField id=\"ref-3\"&gt;Bill&lt;/_x003C_Name_x003E_k__BackingField&gt;
        /// //     &lt;/a1:GenericListExtensionTest_x002B_Person&gt;
        /// //   &lt;/SOAP-ENV:Body&gt;
        /// // &lt;/SOAP-ENV:Envelope&gt;        
        /// s = new Person("Bill").ToString(FormatOption.Base32Soap);
        /// 
        /// // AAEAAAD/////AQAAAAAAAAAMAgAAAFVFbnRlcnByaXNlLkNvcmUuRXh0ZW5zaW9uLlRlc3QsIFZlcnNpb249MS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXl
        /// // Ub2tlbj1udWxsBQEAAAA+RW50ZXJwcmlzZS5Db3JlLkV4dGVuc2lvbi5UZXN0LkdlbmVyaWNMaXN0RXh0ZW5zaW9uVGVzdCtQZXJzb24BAAAAFTxOYW1lPmtfX0JhY2tpbm
        /// // dGaWVsZAECAAAABgMAAAAEQmlsbAs=
        /// s = new Person("Bill").ToString(FormatOption.Base64Binary);
        /// 
        /// // PFNPQVAtRU5WOkVudmVsb3BlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzZD0iaHR0cDovL3d3dy53My5v
        /// // cmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOlNPQVAtRU5DPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy9zb2FwL2VuY29kaW5nLyIgeG1sbnM6U09BUC1FTlY9Imh0dHA
        /// // 6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3NvYXAvZW52ZWxvcGUvIiB4bWxuczpjbHI9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vc29hcC9lbmNvZGluZy9jbHIvMS
        /// // 4wIiBTT0FQLUVOVjplbmNvZGluZ1N0eWxlPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy9zb2FwL2VuY29kaW5nLyI+DQo8U09BUC1FTlY6Qm9keT4NCjxhMTpHZW5lcm
        /// // ljTGlzdEV4dGVuc2lvblRlc3RfeDAwMkJfUGVyc29uIGlkPSJyZWYtMSIgeG1sbnM6YTE9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vY2xyL25zYXNzZW0vRW50ZX
        /// // JwcmlzZS5Db3JlLkV4dGVuc2lvbi5UZXN0L0VudGVycHJpc2UuQ29yZS5FeHRlbnNpb24uVGVzdCUyQyUyMFZlcnNpb24lM0QxLjAuMC4wJTJDJTIwQ3VsdHVyZSUzRG5ld
        /// // XRyYWwlMkMlMjBQdWJsaWNLZXlUb2tlbiUzRG51bGwiPg0KPF94MDAzQ19OYW1lX3gwMDNFX2tfX0JhY2tpbmdGaWVsZCBpZD0icmVmLTMiPkJpbGw8L194MDAzQ19OYW1l
        /// // X3gwMDNFX2tfX0JhY2tpbmdGaWVsZD4NCjwvYTE6R2VuZXJpY0xpc3RFeHRlbnNpb25UZXN0X3gwMDJCX1BlcnNvbj4NCjwvU09BUC1FTlY6Qm9keT4NCjwvU09BUC1FTlY6
        /// // RW52ZWxvcGU+DQo=
        /// s = new Person("Bill").ToString(FormatOption.Base64Soap);
        /// 
        /// // &lt;GenericListExtensionTest.Person 
        /// //   xmlns=\"http://schemas.datacontract.org/2004/07/NazTek.Extension.Clr4.Test\" 
        /// //   xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"&gt;
        /// //   &lt;_x003C_Name_x003E_k__BackingField&gt;Bill&lt;/_x003C_Name_x003E_k__BackingField&gt;
        /// // &lt;/GenericListExtensionTest.Person&gt;
        /// s = new Person("Bill").ToString(FormatOption.Pox);
        /// </code>
        /// </example>
        public static string ToString(this object serializableInstance, FormatOption formatOption)
        {
            const int MAX_REATTEMPT_COUNT = 3;
            int iReattemptCount = 1;
            string sSerialString = ToString(formatOption, serializableInstance);
            while (formatOption == FormatOption.Base64Binary && sSerialString.Contains(" "))
            {
                iReattemptCount += 1;
                if (iReattemptCount > MAX_REATTEMPT_COUNT)
                {
                    throw new CustomException(Common.GetMessage(StringResource.Default.Msg_InvalidSerializationCharacter, MAX_REATTEMPT_COUNT, sSerialString));
                }
                sSerialString = ToString(formatOption, serializableInstance);
            }
            return sSerialString;
        }
        /// <summary> 
        /// Copies state from one instance to another, based on properties with the same name and convertible types 
        /// </summary> 
        /// <param name="from">Object instance to copy state from</param> 
        /// <param name="to">Object instance to copy state to</param> 
        /// <remarks>May be used to upcast, where subclass specific state would be unchanged</remarks> 
        /// <example>
        /// <code>
        /// // context
        /// internal class Person
        /// {
        ///     internal string Name { get; set; }
        ///     internal int Age { get; set; }
        ///     internal Person(string pName, int pAge)
        ///     {
        ///         this.Name = pName;
        ///         this.Age = pAge;
        ///     }
        /// }
        /// internal class Senior : Person
        /// {
        ///     internal bool IsRetired { get; set; }
        ///     internal Senior() : this("", 100) { }
        ///     internal Senior(string pName, int pAge) : base(pName, pAge) 
        ///     {
        ///         if (pAge &lt; 65)
        ///             throw new Exception("too young");
        ///     }
        /// }
        /// 
        /// // usage
        /// Senior senior = new Senior(); // Senior {Name="", Age=100, IsRetired=false}
        /// new Person("Bob", 35).CopyTo(senior); // Senior {Name="Bob", Age=35, IsRetired=false}
        /// </code>
        /// </example>
        public static void CopyTo(this object from, object to)
        {
            Parameter.Validate<object>("to", to);

            Type oTargetType = to.GetType();
            foreach (PropertyInfo prop in from.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                if (prop.CanRead && prop.CanWrite && prop.GetIndexParameters().Length == 0)
                    prop.SetValue(to, prop.GetValue(from, null), null);
        }
        /// <summary>
        /// Writes object state to Console.Out
        /// </summary>
        /// <param name="instance">The object to output the state of</param>
        /// <example>
        /// <code>
        /// // context
        /// internal class Person
        /// {
        ///     internal string Name { get; set; }
        ///     internal int Age { get; set; }
        ///     internal Person(string pName, int pAge)
        ///     {
        ///         this.Name = pName;
        ///         this.Age = pAge;
        ///     }
        /// }
        /// 
        /// // usage
        /// Person person = new Person("Bob", 35).Reflect(); // Person {Name:"Bob", Age:35,}
        /// </code>
        /// </example>
        public static void Reflect(this object instance)
        {
            Reflect(instance, Console.Out);
        }
        /// <summary>
        /// Writes object state to specified writer
        /// </summary>
        /// <param name="instance">The object to output the state of</param>
        /// <param name="writer">The writer to output the object state to</param>
        /// <example>
        /// <code>
        /// // context
        /// internal class Person
        /// {
        ///     internal string Name { get; set; }
        ///     internal int Age { get; set; }
        ///     internal Person(string pName, int pAge)
        ///     {
        ///         this.Name = pName;
        ///         this.Age = pAge;
        ///     }
        /// }
        /// 
        /// // usage
        /// Person person = new Person("Bob", 35).Reflect(); // Person {Name:"Bob", Age:35,}
        /// </code>
        /// </example>
        public static void Reflect(this object instance, TextWriter writer)
        {
            Parameter.Validate<TextWriter>("writer", writer);

            IEnumerable enumerable = instance as IEnumerable;
            if (enumerable != null)
                foreach (var elem in enumerable)
                    Reflect(elem, writer);
            foreach (PropertyInfo item in instance.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
            {
                ParameterInfo[] parameters = item.GetIndexParameters();
                if (parameters == null)
                    writer.WriteLine("{0} : {1}", item.Name, item.GetValue(instance, null));
                else
                    writer.WriteLine("{0} [indexed property] : {1}", item.Name, item.ToString());
            }
            writer.WriteLine(instance);
        }
        /// <summary>
        /// Determines whether a values is defined in an enum
        /// </summary>
        /// <typeparam name="T">The enum type to compare the value in</typeparam>
        /// <param name="enumValue">The value to compare in the enum type</param>
        /// <returns>True, when the specified value is found in the specified enum type</returns>
        public static bool IsDefinedInEnum<T>(this object enumValue)
        {
            return Enum.IsDefined(typeof(T), enumValue);
        }

        /// <summary> 
        /// Serializes an object to string. The object must be serializable via Serializable attribute or ISerializable interface 
        /// </summary> 
        /// <param name="formatOption">Formatting to use, e.g., base 32/64 and soap/binary</param> 
        /// <param name="serializableInstance">The instance to serialize</param> 
        /// <returns>A string representation of the the object</returns> 
        /// <remarks></remarks> 
        private static string ToString(FormatOption formatOption, object serializableInstance)
        {
            switch (formatOption)
            {
                case FormatOption.Base32Binary:
                    return ToString(serializableInstance, new BinaryFormatter());
                case FormatOption.Base32Soap:
                    return ToString(serializableInstance, new SoapFormatter());
                case FormatOption.Base64Binary:
                    return Convert.ToBase64String(Encoding.Default.GetBytes(ToString(serializableInstance, new BinaryFormatter())));
                case FormatOption.Base64Soap:
                    return Convert.ToBase64String(Encoding.Default.GetBytes(ToString(serializableInstance, new SoapFormatter())));
                case FormatOption.Pox:
                    return ToString(serializableInstance);
                default:
                    throw new CustomException(StringResource.Default.Msg_UnsupportedFormat);
            }
        }
        /// <summary> 
        /// Serilaizes an instance of an object to string. The object must be serializable via Serializable attribute or ISerializable interface 
        /// </summary> 
        /// <param name="serializableInstance">The instance of the object to serialize</param> 
        /// <param name="formatter">Binary or Soap</param>
        /// <returns>A 32-bit string representation of the object instance</returns> 
        /// <remarks></remarks> 
        private static string ToString(object serializableInstance, IFormatter formatter)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                formatter.Serialize(stream, serializableInstance);
                return stream.ToString(Encoding.Default);
            }
        }
        /// <summary> 
        /// Serilaizes an instance of an object to a POX string 
        /// </summary> 
        /// <param name="serializableInstance">The instance of the object to serialize</param> 
        /// <returns>A POX string representation of the object instance</returns> 
        /// <remarks></remarks> 
        private static string ToString(object serializableInstance)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                DataContractSerializer serializer = new DataContractSerializer(serializableInstance.GetType());
                serializer.WriteObject(stream, serializableInstance);
                stream.Position = 0L;
                return Encoding.Default.GetString(stream.ToArray());
            }
        }
    }
}